package alittlepond;

import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JPanel;

public class BlurPanel extends JPanel {

	private final int width, height;
	private double[][] values;
	private boolean redrawRequired = true;

	private Thing thing;
	
	private static final int BLUR_RADIUS = 4;
	private static final double DECAY_RATE = 0.999;
	public BlurPanel(int width, int height) {
		super();
		this.width = width;
		this.height = height;

		setPreferredSize(new Dimension(width, height));
		values = new double[width][height];
		thing = new Thing(50,50,this);

		TimerTask drawTask = new TimerTask() {

			@Override
			public void run() {
//				if (redrawRequired) {
				thing.move();
				blur(10);
				doRedraw();
//				}
			}

		};
		
		
		
		
		Timer timer = new Timer(true);
		timer.schedule(drawTask, 500, 50);
		MouseAdapter ma = new MouseAdapter() {

//			private boolean down = false;
//			
//			@Override
//			public void mouseMoved(MouseEvent e) {
//				System.out.println("moved");
//			if(down) {
//				int x = e.getX();
//			
//				int y = e.getY();
//				for (int i = -5; i <= 5; i++ ) {
//					for(int j = -5; j <= 5; j++) {
//						values[getWrappedX(x + i)][getWrappedY(y + j)] = 0.99;
//						
//					}
//				}
//				redrawRequired = true;
//			}	
//			}

//			@Override
//			public void mousePressed(MouseEvent e) {
//				System.out.println("pressed");
//				down = true;
//			}
//
//			@Override
//			public void mouseReleased(MouseEvent e) {
//				System.out.println("released");
//				down = false;
//			}
//
			@Override
			public void mouseClicked(MouseEvent e) {
				drawBlob(e.getX(), e.getY());
			}

			@Override
			public void mouseDragged(MouseEvent e) {
//				int x = e.getX();
//				
//				int y = e.getY();
				drawBlob(e.getX(), e.getY());
			}
//
//			@Override
//			public void mouseEntered(MouseEvent e) {
//				System.out.println("en");
//			}
//
//			@Override
//			public void mouseExited(MouseEvent e) {
//				System.out.println("ex");
//			}
//
//			@Override
//			public void mouseWheelMoved(MouseWheelEvent e) {
//				System.out.println("wm");
//			}

			
		
		};

		addMouseListener(ma);
		addMouseMotionListener(ma);
		addMouseWheelListener(ma);
	}

	private void drawBlob(int x, int y) {
		for (int i = -5; i <= 5; i++ ) {
			for(int j = -5; j <= 5; j++) {
				values[getWrappedX(x + i)][getWrappedY(y + j)] = 0.99;
				
			}
		}
		redrawRequired = true;
		
	}
	
	@Override
	public void repaint() {
		redrawRequired = true;
	}

	private void doRedraw() {
		final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		doSomethingForEachPoint(new PointExecutor() {

			@Override
			public void doSomething(int x, int y) {
				bi.setRGB(x, y, getRgb(x, y));
			}
		});
		thing.draw(bi.getGraphics());
		
		getGraphics().drawImage(bi, 0, 0, width, height, this);
		redrawRequired = false;

	}

	private int getRgb(int x, int y) {
		int v = (int) (values[x][y] * 256);

		return ((v & 0x0ff) << 16) | ((v & 0x0ff) << 8) | (v & 0x0ff);
	}

	interface PointExecutor {
		void doSomething(int x, int y);
	}

	private void doSomethingForEachPoint(PointExecutor executor) {
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				executor.doSomething(x, y);
			}
		}
	}

	private int getWrappedX(int x) {
		return wrapValue(x, width);
	}

	private int getWrappedY(int y) {
		return wrapValue(y, height);
	}

	private static int wrapValue(int value, int maxPlusOne) {
		if (value < 0) {
			return maxPlusOne + (value % maxPlusOne);
		}
		if (value >= maxPlusOne) {
			return value % maxPlusOne;
		}
		return value;
	}
	private void blur(int r) {
//		int height = in[0].length;
//		int width = in.length;
//		int diam = 2 * r + 1;
//		double[][] scaled = new double[width][height];
//
//
//		for (int x = 0; x < width; x++) {
//			for (int y = 0; y < height; y++) {
//				scaled[x][y] = values[x][y] / diam;
//			}
//		}
//
//		
//		
//		for (int y = 0; y < height; ++y) {
//			
//			double total = 0;
//			for (int i = -r; i <= r; ++i) {
//				total += scaled[getWrappedX(i)][y];
//			}
//			
//			values[0][y] = total;
//			for (int x = 1; x < width; ++x) {
//				double oldValue = scaled[getWrappedX(x - r -1)][y];
//				double newValue = scaled[getWrappedX(x + r)][y];
//				total = total + newValue - oldValue;
//				values[x][y] = total;
//				
//
//			}
//		}


		double[][] tmp = blurHorizAndTranspose(values, width, height, BLUR_RADIUS);
		values = blurHorizAndTranspose(tmp, height, width, BLUR_RADIUS);
		doSomethingForEachPoint(new PointExecutor() {
			
			@Override
			public void doSomething(int x, int y) {
				values[x][y] = values[x][y] * DECAY_RATE;
			}
		});
	}

	private static double[][] blurHorizAndTranspose(double[][] in, int width, int height, int r) {
		int diam = 2 * r + 1;
		double[][] scaled = new double[width][height];
		double[][] outTransposed = new double[height][width];

		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				scaled[x][y] = in[x][y] / diam;
			}
		}

		
		
		for (int y = 0; y < height; ++y) {
			
			double total = 0;
			for (int i = -r; i <= r; ++i) {
				total += scaled[wrapValue(i, width)][y];
			}
			
			outTransposed[y][0] = total;
			for (int x = 1; x < width; ++x) {
				double oldValue = scaled[wrapValue(x - r -1, width)][y];
				double newValue = scaled[wrapValue(x + r, width)][y];
				total = total + newValue - oldValue;
				outTransposed[y][x] = total;
				

			}
		}
		return outTransposed;
	}
	public double getValue(int x, int y) {
		return values[getWrappedX(x)][getWrappedY(y)];
	}
	
	public int getX(int x) {
		return getWrappedX(x);
	}
	public int getY(int y) {
		return getWrappedY(y);
	}
}
